package com.itita.ww2.utils.channel.facebook;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.itita.ww2.utils.TimeUtil;
import com.itita.ww2.utils.channel.facebook.FBTokenVerifier.Builder.AccessType;

import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class FBTokenVerifier {
	private static final Logger logger = LoggerFactory.getLogger(FBTokenVerifier.class);
	private Builder builder;

	private FBTokenVerifier(Builder builder) {
		this.builder = builder;
	}

	public FBUserInfo verify(String token) {
		if (builder.useGenericAccessToken) {
			String accessToken = getAccessToken();
			if (accessToken != null) {
				builder.accessToken = accessToken;
			}
		}
		// get FBUserInfo
		FBUserInfo userInfo = null;
		if (builder.accessType == AccessType.DEBUG_TOKEN) {
			userInfo = getUserInfoByDebugToken(token);
		} else {
			userInfo = getUserInfoByMe(token);
		}
		return userInfo;
	}

	private String getAccessToken() {
		OkHttpClient httpClient = new OkHttpClient.Builder().proxy(builder.proxy).build();

		// @formatter:off
		HttpUrl url = new HttpUrl.Builder()
				.scheme("https")
				.host(builder.graph_host)
				.addPathSegment(builder.tokenPath)
				.addQueryParameter("client_id", builder.clientId)
				.addQueryParameter("client_secret", builder.clientSecret)
				.addQueryParameter("grant_type", builder.grantType)
				.build();
		
		Request request = new Request.Builder().url(url).build();
		// @formatter:on

		try {
			Response response = httpClient.newCall(request).execute();
			if (!response.isSuccessful()) {
				logger.error("get access_token error! body:{}", response.body().string());
				return null;
			}
			String body = response.body().string();
			if (body.contains("access_token")) {
				return body.replaceAll("access_token=", "");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private FBUserInfo getUserInfoByMe(String token) {
		FBUserInfo ret = new FBUserInfo();
		OkHttpClient httpClient = new OkHttpClient.Builder().proxy(builder.proxy).build();

		// @formatter:off
		HttpUrl url = new HttpUrl.Builder()
				.scheme(builder.scheme)
				.host(builder.graph_host)
				.addPathSegment(builder.userInfoPath)
				.addQueryParameter("access_token", token)
				.addQueryParameter("fields", "id,name,email,verified")
				.build();
		
		Request request = new Request.Builder().url(url).build();
		// @formatter:on
		try {
			Response response = httpClient.newCall(request).execute();
			if (!response.isSuccessful()) {
				logger.error("get userinfo error! body:{}", response.body().string());
				return ret;
			}
			ret = new GsonBuilder().create().fromJson(response.body().string(), FBUserInfo.class);
			if (ret.getId() != null) {
				ret.setSucc(true);
			}
			return ret;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ret;
	}

	private FBUserInfo getUserInfoByDebugToken(String token) {
		FBUserInfo ret = new FBUserInfo();
		OkHttpClient httpClient = new OkHttpClient.Builder().proxy(builder.proxy).build();
		
		// @formatter:off
		HttpUrl url = new HttpUrl.Builder()
				.scheme(builder.scheme)
				.host(builder.graph_host)
				.addPathSegment(builder.debugTokenPath)
				.addQueryParameter("input_token", token)
				.addQueryParameter("access_token", builder.accessToken)
				.build();
		
		Request request = new Request.Builder().url(url).build();
		// @formatter:on
		
		try {
			Response response = httpClient.newCall(request).execute();
			if (!response.isSuccessful()) {
				logger.error("get userinfo error! body:{}", response.body().string());
				return ret;
			}
			JsonObject jsonObject = new JsonParser().parse(response.body().string()).getAsJsonObject();
			if (!jsonObject.has("data")) {
				logger.error("get userinfo error! body:{}", response.body().string());
				return ret;
			}
			JsonObject m_data = jsonObject.get("data").getAsJsonObject();
			if (m_data.has("error")) {
				logger.error("get userinfo error! error:{}", m_data.get("error"));
				return ret;
			}
			// 检查is_valid & expires_at
			boolean isValid = m_data.get("is_valid").getAsBoolean();
			if (!isValid) {
				logger.error("get userinfo error! is_valid = false");
				return ret;
			}
			long expires_at = m_data.get("expires_at").getAsLong();
			long now = TimeUtil.getUtcNowMillis() / 1000;
			if (now > expires_at) {
				logger.error("get userinfo error! expires timeout!");
				return ret;
			}
			ret.setId(m_data.get("user_id").getAsString());
			ret.setSucc(true);
			return ret;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ret;
	}

	public static class Builder {
		private final String scheme = "https";
		private final String graph_host = "graph.facebook.com";
		private final String tokenPath = "oauth/access_token";
		private final String userInfoPath = "me";
		private final String debugTokenPath = "debug_token";
		private final String grantType = "client_credentials";
		//
		private String clientId;
		private String clientSecret;
		// 
		private boolean useGenericAccessToken; // 是否使用动态请求出的access_token
		private String accessToken;
		//
		private AccessType accessType = AccessType.DEBUG_TOKEN;
		// 
		private Proxy proxy = Proxy.NO_PROXY;

		public static enum AccessType {
			DEBUG_TOKEN, ME,
		}

		public Builder(String clientId, String clientSecret) {
			this.clientId = clientId;
			this.clientSecret = clientSecret;
			this.useGenericAccessToken = false;
			this.accessToken = clientId + "|" + clientSecret;
		}

		public Builder setUseGenericAccessToken(boolean val) {
			this.useGenericAccessToken = val;
			return this;
		}

		public Builder setAccessType(AccessType type) {
			this.accessType = type;
			return this;
		}

		public Builder setProxy(String type, String host, int port) {
			this.proxy = new Proxy(Type.valueOf(type.toUpperCase()), new InetSocketAddress(host, port));
			return this;
		}

		public FBTokenVerifier build() {
			return new FBTokenVerifier(this);
		}
	}

	public Builder getBuilder() {
		return builder;
	}
}
